package cn.hcbycloud.common.permission.aop;

import cn.hcbycloud.common.core.exception.CustomizeException;
import cn.hcbycloud.common.jwt.vo.LoginInfo;
import cn.hcbycloud.common.permission.annotation.PermissionsRequires;
import cn.hcbycloud.common.permission.annotation.RolesRequires;
import cn.hcbycloud.common.permission.constant.Relation;
import cn.hcbycloud.common.permission.utils.AccessContext;
import cn.hcbycloud.common.permission.service.PermissionCheckService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author gaoh
 * @desc 注解鉴权AOP
 * @date 2022年02月01日 下午 3:10
 */
@Slf4j
@Aspect
@AllArgsConstructor
public class PermissionCheckAspect {
    private final static String TOKEN_EXCEPTION_MESSAGE = "认证中心令牌校验错误";
    private final static String PERMISSION_EXCEPTION_MESSAGE = "认证中心权限校验错误";
    private final static String ROLE_EXCEPTION_MESSAGE = "认证中心角色校验错误";
    /**
     * 由依赖项目实现的权限角色校验接口
     */
    private PermissionCheckService permissionCheckService;

    /**
     * 切入点
     */
    @Pointcut(value = "@annotation(cn.hcbycloud.common.permission.annotation.PermissionsRequires)" +
            " || @annotation(cn.hcbycloud.common.permission.annotation.RolesRequires)")
    private void permissionCheckCut() {

    }

    /**
     * 定义了切面的处理逻辑
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("permissionCheckCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = signature.getName();
        log.info("className:{},methodName:{}", className, methodName);
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        if (targetMethod.isAnnotationPresent(PermissionsRequires.class) || targetMethod.isAnnotationPresent(RolesRequires.class)) {
            LoginInfo loginInfo = AccessContext.getLoginInfo();
            // 接口添加的权限注解，但没有认证信息，没有认证信息没法鉴权，抛出异常
            if (ObjectUtil.isNull(loginInfo)) {
                throw new CustomizeException(TOKEN_EXCEPTION_MESSAGE, HttpStatus.SC_UNAUTHORIZED);
            }
            // 判断接口是否注解了权限注解
            if (targetMethod.isAnnotationPresent(PermissionsRequires.class)) {
                //获取方法上注解中标注的权限列表
                PermissionsRequires permissionsRequires = targetMethod.getAnnotation(PermissionsRequires.class);
                List<String> permissionList = Arrays.asList(permissionsRequires.value());
                Relation relation = permissionsRequires.relation();
                if (CollUtil.isNotEmpty(permissionList)) {
                    // 校验权限是否匹配
                    checkPermissions(loginInfo, permissionList, relation);
                }
            }
            // 判断接口是否注解了角色注解
            if (targetMethod.isAnnotationPresent(RolesRequires.class)) {
                //获取方法上注解中标注的角色列表
                RolesRequires rolesRequires = targetMethod.getAnnotation(RolesRequires.class);
                List<String> roleList = Arrays.asList(rolesRequires.value());
                Relation relation = rolesRequires.relation();
                if (CollUtil.isNotEmpty(roleList)) {
                    // 校验角色是否匹配
                    checkRoles(loginInfo, roleList, relation);
                }
            }
        }
        return pjp.proceed();
    }

    /**
     * 校验注解中角色列表与认证中心返回的角色列表是否匹配
     *
     * @param loginInfo
     * @param roleList
     * @param relation
     */
    private void checkRoles(LoginInfo loginInfo, List<String> roleList, Relation relation) {
        List<String> rolesFromAuth = permissionCheckService.getRoles(loginInfo);
        // 如注解存在角色，但用户没有任何任何，代表没有权限，抛出异常
        if (CollUtil.isEmpty(rolesFromAuth)) {
            throw new CustomizeException(ROLE_EXCEPTION_MESSAGE, HttpStatus.SC_UNAUTHORIZED);
        } else {
            checkRealtion(roleList, relation, rolesFromAuth, ROLE_EXCEPTION_MESSAGE);
        }
    }

    /**
     * 校验注解中权限列表与认证中心返回的权限列表是否匹配
     *
     * @param loginInfo
     * @param permissionList
     * @param relation
     */
    private void checkPermissions(LoginInfo loginInfo, List<String> permissionList, Relation relation) {
        List<String> permissionsFromAuth = permissionCheckService.getPermissions(loginInfo);
        // 如注解存在权限，但用户没有任何权限，代表没有权限，抛出异常
        if (CollUtil.isEmpty(permissionsFromAuth)) {
            throw new CustomizeException(PERMISSION_EXCEPTION_MESSAGE, HttpStatus.SC_UNAUTHORIZED);
        } else {
            checkRealtion(permissionList, relation, permissionsFromAuth, PERMISSION_EXCEPTION_MESSAGE);
        }
    }

    /**
     * 根据注解中的关系注解进行权限或角色的列表匹配
     *
     * @param list
     * @param relation
     * @param fromAuthList
     * @param exceptionMessage
     */
    private void checkRealtion(List<String> list, Relation relation, List<String> fromAuthList, String exceptionMessage) {
        /*如注解中标注的多个角色为或的关系，只要有一个角色被认证中心返回的角色命中，就通过角色校验*/
        if (Relation.OR == relation) {
            boolean flag = false;
            for (String str : list) {
                if (fromAuthList.contains(str)) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                throw new CustomizeException(exceptionMessage, HttpStatus.SC_UNAUTHORIZED);
            }
        }
        /* 如注解中标注的多个角色为与的关系，则只要存在一个角色不在认证中心返回的角色中，就代表角色校验不通过*/
        if (Relation.AND == relation) {
            for (String str : list) {
                if (!fromAuthList.contains(str)) {
                    throw new CustomizeException(exceptionMessage, HttpStatus.SC_UNAUTHORIZED);
                }
            }
        }
    }
}